import random
from threading import Lock

class Network(object):    
    def __init__(self, my_addr, brook):
        self._lock = Lock()
        
        self.my_addr = my_addr
        self._brook = brook
        
        self._known_peers = set()
        self._peer_data = {}
        self._chunk_rarity = []
        for _ in range(0, self._brook.number_of_chunks+1):
            self._chunk_rarity.append(0)
        self.active_leeches_from_peer = {}
        
    #adding peer with address "addr". Will not be added if addr is None or yourself.
    def add_peer(self, addr):
        with self._lock:
            if not addr == self.my_addr and not addr == None:
                print "adding peer: ", addr
                self._known_peers.add(addr)
                self._fill_peer_data(addr)
                self.active_leeches_from_peer[addr] = 0
    
    def add_peer_list(self, peers):
        for peer in peers:
            self.add_peer(peer)

    #before we know anything about what the peer have, it's assumed the peer does not have anything
    def _fill_peer_data(self, peer):
        print "Filling peer data for: ", peer
        (ip, port) = peer
        peer = ip + ":" + str(port)
        self._peer_data[peer] = []
        for _ in range(0, self._brook.number_of_chunks):
            self._peer_data[peer].append(False)
        self.active_leeches_from_peer[peer] = 0
    
    def remove_peer(self, addr):
        (ip, port) = addr
        peer = ip + ":" + str(port)
        with self._lock:
            self._known_peers.remove(addr)
            self._peer_data.pop(peer)
            self.active_leeches_from_peer.pop(peer)
    
    def update_peer_activity(self, addr, starting):
        (ip, port) = addr
        peer = ip + ":" + str(port)
        with self._lock:
            if starting:
                self.active_leeches_from_peer[peer] += 1
            else:
                self.active_leeches_from_peer[peer] -= 1

    def get_peer_data(self, peer_addr):
        (ip, port) = peer_addr
        peer = ip + ":" + str(port)
        return self._peer_data[peer]
    
    #adding peer chunk info. If the peer is already known whatever chunks it knows must be removed from chunk_rarity
    def set_peer_data(self, peer_addr, data):
        with self._lock:
            (ip, port) = peer_addr
            peer = ip + ":" + str(port)
            #remove chunk_rarity for the peer we might be about to update
            if self._peer_data.has_key(peer):
                old_peer_data = self._peer_data[peer]
                idx = 0
                for chunk in old_peer_data:
                    if chunk and not chunk == None and not self._chunk_rarity[idx] == None:
                        self._chunk_rarity[idx] -= 1
                    idx += 1
            idx = 0
            #update rarety with new peer data
            for chunk in data:
                if chunk and not self._chunk_rarity[idx] == None:
                    self._chunk_rarity[idx] += 1
                idx += 1
            self._peer_data[peer] = data
    
    #returns a random peer, if none is available None is returned
    def get_random_peer(self):
        
        if len(self._known_peers) == 0:
            return None
        else:
            return random.choice(list(self._known_peers))
    
    def get_all_peers(self):
        return self._known_peers
    
    def find_peer_with_chunk(self, chunk_idx):
        tmp = set()
        with self._lock:
            for peer in self._known_peers:
                (ip, port) = peer
                key = ip + ":" + str(port)
                #some times a race condition results in self._peer_data[key] is empty
                #check for len added to avoid it crashing on that
                if len(self._peer_data[key]) == self._brook.number_of_chunks+1 and self._peer_data[key][chunk_idx]: 
                    tmp.add(peer)
        if len(tmp) == 0:
            return None
        else:
            return random.choice(list(tmp))
    
    def know_peer(self, peer):
        if peer == self.my_addr:
            return True
        else:
            return peer in self._known_peers